﻿/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Threading;

namespace NTasking.Internals
{

    /// <summary>
    /// The <see cref="ManagedLock"/> class represents an alternative to the <see cref="Monitor"/> class.
    /// Its advantage is that it allows to trace which thread has acquired the lock and which thread
    /// is blocked trying to acquire the lock or is waiting for the lock.
    /// If TASKING_DEBUG is not defined, this class is nothing more than a wrapper around the <see cref="Monitor"/> class.
    /// </summary>
    internal class ManagedLock
    {

        #region Fields

        /// <summary>
        /// The object used to actually perform the locking/unlocking
        /// </summary>
        private readonly object _lockObject = new object();


        private readonly CodeWrapper _lockWrapper;

        private readonly CodeWrapper _unlockWrapper;


        #endregion


        #region Properties

#pragma warning disable 1587

#if TASKING_DEBUG

        /// <summary>
        /// Gets or sets the name of this instance.
        /// </summary>
        /// <value>The name of this instance.</value>
        private string Name { get; set; }


        /// <summary>
        /// Gets or sets the object which created this instance.
        /// </summary>
        /// <value>The object which created this instance.</value>
        private object Referrer { get; set; }


        /// <summary>
        /// Gets or sets the count of how many recursive calls to Lock have been made on this instance.
        /// </summary>
        /// <value>The number of recursive calls to Lock.</value>
        private int LockCount { get; set; }


        /// <summary>
        /// Gets or sets the thread which is currently owning the lock.
        /// </summary>
        /// <value>The thread which is currently owning the lock.</value>
        private ThreadWrapper LockOwner { get; set; }

#endif

#pragma warning restore 1587

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ManagedLock"/> class.
        /// </summary>
        /// <param name="name">The name of this instance.</param>
        /// <param name="referrer">The class which is protected by this instance.</param>
// ReSharper disable UnusedParameter.Local
        public ManagedLock(string name, object referrer)
// ReSharper restore UnusedParameter.Local
        {
            _lockWrapper = new CodeWrapper(GetLock, ReleaseLock);
            _unlockWrapper = new CodeWrapper(ReleaseLock, GetLock);

#if TASKING_DEBUG
            Name = name;
            Referrer = referrer;
#endif
        }

        #endregion


        #region Methods

        /// <summary>
        /// Acquires an exclusive lock on this instance.
        /// </summary>
        internal void GetLock()
        {
#if TASKING_DEBUG
            bool entered = false;
            ThreadWrapper currentThread = ThreadWrapper.CurrentThread;
            try
            {
                if (!Monitor.TryEnter(_lockObject))
                {
                    Tracer.Trace("Blocked on acquisition of " + this + " lock");
                    Monitor.Enter(_lockObject);
                }
                entered = true;
            }
            finally
            {
                if (entered)
                {
                    if (++LockCount == 1)
                    {
                        LockOwner = currentThread;
                        Tracer.Trace("Acquired " + this + " lock");
                    }
                    else
                    {
                        Tracer.Trace("Nested acquire " + this + " lock count=" + LockCount);
                    }
                }
            }
#else
            Monitor.Enter(_lockObject);
#endif
        }


        /// <summary>
        /// Attempts to acquire an exclusive lock on this instance.
        /// </summary>
        /// <returns></returns>
        internal bool TryGetLock()
        {
#if TASKING_DEBUG
            bool entered = false;

            try
            {
                entered = Monitor.TryEnter(_lockObject);
            }
            finally
            {
                if (entered)
                {
                    if (++LockCount == 1)
                    {
                        LockOwner = ThreadWrapper.CurrentThread;
                        Tracer.Trace("Acquired " + this + " lock");
                    }
                    else
                    {
                        Tracer.Trace("Nested acquire " + this + " lock count=" + LockCount);
                    }
                }
                else
                    Tracer.Trace("Failed to acquired " + this + " lock");
            }

            return entered;
#else
            return Monitor.TryEnter(_lockObject);
#endif
        }


        /// <summary>
        /// Releases an exclusive lock on this instance.
        /// </summary>
        internal void ReleaseLock()
        {
#if TASKING_DEBUG
            if (LockOwner != ThreadWrapper.CurrentThread)
            {
                if (LockOwner == null)
                    Errors.FatalError("Calling " + this + ".ReleaseLock when lock owner is NULL & count=" + LockCount);
                else
                    Errors.FatalError("Calling " + this + "ReleaseLock when lock owner is " + LockOwner + " & count=" + LockCount);
            }

            if (LockCount == 0)
            {
                Errors.FatalError("Calling ReleaseLock when count=0");
            }

            try
            {
            }
            finally
            {
                if (--LockCount == 0)
                {
                    Tracer.Trace("Released " + this + " lock");
                    LockOwner = null;
                }
                else
                {
                    Tracer.Trace("Nested released " + this + " lock count=" + LockCount);
                }

                Monitor.Exit(_lockObject);
            }
#else
            Monitor.Exit(_lockObject);
#endif
        }


        /// <summary>
        /// Get a <see cref="CodeWrapper"/> in which the statements will be executed with a GetLock/ReleaseLock on this instance.
        /// </summary>
        /// <returns>The new code wrapper</returns>
        internal CodeWrapper Locked()
        {
            return _lockWrapper.Enter();
        }


        /// <summary>
        /// Get a <see cref="CodeWrapper"/> in which the statements will be executed with a ReleaseLock/GetLock on this instance.
        /// </summary>
        /// <returns>The new code wrapper</returns>
        internal CodeWrapper Unlocked()
        {
            return _unlockWrapper.Enter();
        }


        /// <summary>
        /// Notifies a thread in the waiting queue of a change in this instance's state.
        /// </summary>
        internal void Pulse()
        {
            using (Locked())
            {
                Tracer.Trace("Waking up " + this);
                Monitor.Pulse(_lockObject);
            }
        }


#if TASKING_DEBUG
        private void SaveState(out int count, out ThreadWrapper owner)
        {
            try
            {
            }
            finally
            {
                // Do everything in a finally to protect against abortion of executing task

                count = LockCount;
                owner = LockOwner;

                LockCount = 0;
                LockOwner = null;
            }
        }
#endif


        /// <summary>
        /// Releases the lock on this instance and blocks the current thread until it reacquires the lock.
        /// </summary>
        internal void WaitLock()
        {
#if TASKING_DEBUG
            if (LockOwner != ThreadWrapper.CurrentThread)
                Errors.FatalError("Calling " + this + ".WaitLock when not owning the lock.");

            int currentCount = 0;
            ThreadWrapper currentOwner = null;

            try
            {
                SaveState(out currentCount, out currentOwner);

                Tracer.Trace("Waiting " + this + " lock");

                Monitor.Wait(_lockObject);
            }
            finally
            {
                LockOwner = currentOwner;
                LockCount = currentCount;
                Tracer.Trace("Re-acquired " + this + " lock count=" + LockCount);
            }
#else
            Monitor.Wait(_lockObject);
#endif
        }


        /// <summary>
        /// Releases the lock on this instance and blocks the current thread until it reacquires the lock.
        /// If the specified timeout interval elapses the thread enters the ready queue.
        /// </summary>
        /// <param name="timeout">The timeout.</param>
        /// <returns></returns>
        internal bool WaitLock(DateTime timeout)
        {
#if TASKING_DEBUG
            bool signaled = false;

            if (LockOwner != ThreadWrapper.CurrentThread)
                Errors.FatalError("Calling " + this + ".WaitLock when not owning the lock.");

            int currentCount = 0;
            ThreadWrapper currentOwner = null;

            try
            {
                SaveState(out currentCount, out currentOwner);

                Tracer.Trace("Waiting " + this + " during " + Math.Max(0, (int)((timeout - DateTime.Now).TotalMilliseconds)));

                signaled = Monitor.Wait(_lockObject, Math.Max(0, (int)((timeout - DateTime.Now).TotalMilliseconds)));
            }
            finally
            {
                LockOwner = currentOwner;
                LockCount = currentCount;

                if (signaled)
                    Tracer.Trace("Re-acquired " + this + " lock after signal lock count=" + LockCount);
                else
                    Tracer.Trace("Re-acquired " + this + " lock after timeout or abort lock count=" + LockCount);
            }
            return signaled;
#else
            return Monitor.Wait(_lockObject, Math.Max(0, (int)((timeout - DateTime.Now).TotalMilliseconds)));
#endif
        }


#if TASKING_DEBUG
        public override string ToString()
        {
            return Referrer + "::" + Name;
        }
#endif

        #endregion

    }

}
